<template>
  <div
    :class="['ever_bright_tree', {'ever_bright_tree-root': level === 0 }]"
    @mousemove="onMousemoveHandler"
    @mouseleave="onMouseleaveHandler"
  >
    <div class="ever_bright_tree-search" v-if="level === 0">
      <el-input placeholder="请输入内容" v-model="searchValue" @input="handleInput" class="input-with-select">
        <el-button slot="append" icon="el-icon-search"></el-button>
      </el-input>
    </div>
    <div ref="nodes" class="ever_bright_tree-nodes-list">
      <div v-for="(node, nodeInd) in nodes" :key="nodeInd"
        :id="`ever_bright_tree-node-${node.title}`"
          :class="['ever_bright_tree-node',{'ever_bright_tree-selected': node.isSelected } ]"
      >
        <div
          class="ever_bright_tree-cursor ever_bright_tree-cursor_before"
          @dragover.prevent
          :style="{
            'visibility': cursorPosition &&
              cursorPosition.node.pathStr === node.pathStr &&
              cursorPosition.placement === 'before' ?
              'visible' :
              'hidden',
          }"
        >
          <!-- suggested place for node insertion  -->
        </div>

        <div
            class="ever_bright_tree-node-item"

            @dblclick="emitNodeDblclick(node, $event)"
            @click="emitNodeClick(node, $event)"
            :path="node.pathStr"
            :class="{
            'ever_bright_tree-cursor-hover':
              cursorPosition &&
              cursorPosition.node.pathStr === node.pathStr,

            'ever_bright_tree-cursor-inside':
              cursorPosition &&
              cursorPosition.placement === 'inside' &&
              cursorPosition.node.pathStr === node.pathStr,
          }"
        >
          <div class="ever_bright_tree-title" :style="{'padding-left': `${gap * (node.level - 1) }px`}">
            <span class="ever_bright_tree-toggle" v-if="!node.isLeaf" @click="onToggleHandler($event, node)">
              <slot name="toggle" :node="node">
                <span>
                {{ !node.isLeaf ? (node.isExpanded ? '-' : '+') : '' }}
                </span>
              </slot>
            </span>
            <slot name="title" :node="node">{{ node.title }}</slot>
            


          </div>

          <div class="ever_bright_tree-sidebar"
            @mousedown="onNodeMousedownHandler($event, node)"
            @mouseup="onNodeMouseupHandler($event, node)"
            >

            <slot name="sidebar" :node="node"></slot>
          </div>

        </div>

        <ever-bright-tree
            v-if="node.children && node.children.length && node.isExpanded"
            :value="node.children"
            :level="node.level"
            :parentInd="nodeInd"
            :allowToggleBranch="allowToggleBranch"
            :gap="gap"
            :edgeSize="edgeSize"
            :showBranches="showBranches"
            @dragover.prevent
        >

          <template slot="title" slot-scope="{ node }">
            <slot name="title" :node="node">{{ node.title }}</slot>
          </template>

          <template slot="toggle" slot-scope="{ node }">
            <slot name="toggle" :node="node">
            <span>
               {{ !node.isLeaf ? (node.isExpanded ? '-' : '+') : '' }}
            </span>
            </slot>
          </template>
        
          <template slot="sidebar" slot-scope="{ node }">
            <slot name="sidebar" :node="node"></slot>
          </template>
          


        </ever-bright-tree>

        <div
          class="ever_bright_tree-cursor ever_bright_tree-cursor_after"
          @dragover.prevent
          :style="{
            'visibility': cursorPosition &&
              cursorPosition.node.pathStr === node.pathStr &&
              cursorPosition.placement === 'after' ?
              'visible' :
              'hidden',
          }"
        >
          <!-- suggested place for node insertion  -->
        </div>

      </div>

      <div v-show="isDragging" v-if="isRoot" ref="dragInfo" class="ever_bright_tree-drag-info" >

        <slot name="draginfo">
          Items: {{selectionSize}}
        </slot>
      </div>

    </div>

  </div>
</template>

<script >

export default {
  name: 'ever-bright-tree',
  props: {
    value: {
      type: Array,
      default: () => []
    },
    edgeSize: {
      type: Number,
      default: 3
    },
    showBranches: {
      type: Boolean,
      default: false
    },
    level: {
      type: Number,
      default: 0
    },
    parentInd: {
      type: Number
    },

    allowToggleBranch: {
      type: Boolean,
      default: true
    },
    gap: {
      type: Number,
      default: 20
    },
    multiselectKey: {
      type: [String, Array],
      default: function () {
        return ['ctrlKey', 'metaKey']
      },
      validator: function (value) {
        let allowedKeys = ['ctrlKey', 'metaKey', 'altKey'];
        let multiselectKeys = Array.isArray(value) ? value : [value];
        multiselectKeys = multiselectKeys.filter(keyName => allowedKeys.indexOf(keyName ) !== -1);
        return !!multiselectKeys.length;
      }
    },
    scrollAreaHeight: {
      type: Number,
      default: 70
    },
    maxScrollSpeed: {
      type: Number,
      default: 20
    }
  },

  data() {
    return {
      rootCursorPosition: null,
      scrollIntervalId: 0,
      scrollSpeed: 0,
      lastSelectedNode: null,
      mouseIsDown: false,
      isDragging: false,
      lastMousePos: {x: 0, y: 0},
      preventDrag: false,


      searchValue: "",
      searchResult: [],
      searchResultIndex: 0,
      timeout: null,
      currentValue: this.value
    };
  },

  mounted() {
    if (this.isRoot) {
      document.addEventListener('mouseup', this.onDocumentMouseupHandler);
    }
  },

  beforeDestroy() {
    document.removeEventListener('mouseup', this.onDocumentMouseupHandler);
  },

  watch: {
    value: function (newValue) {
      this.currentValue = newValue;
    }
  },

  computed: {
    cursorPosition() {
      if (this.isRoot) return this.rootCursorPosition;
      return this.getParent().cursorPosition;
    },

    depth() {
      return this.gaps.length
    },

    nodes() {
      if (this.isRoot) {
        const nodeModels = this.copy(this.currentValue);
        return this.getNodes(nodeModels);
      }

      return this.getParent().nodes[this.parentInd].children;
    },


    isRoot() {
      return !this.level
    },

    selectionSize() {
      return this.getSelected().length;
    },

    dragSize() {
      return this.getDraggable().length;
    }
  },
  methods: {
    goNextSearchResultItem(index = -1) {
      console.log(index, this.searchResult[index])
      this.searchResultIndex =  index + 1
      this.select(this.searchResult[this.searchResultIndex].path)

      
    },
    handleInput() {
      if (this.timeout) return;
      this.timeout = setTimeout(() => {
        clearTimeout(this.timeout);
        this.timeout = null;
        this.searchResult = this.search("title", this.searchValue);
        if (this.searchResult.length > 0) {
          this.goNextSearchResultItem()
        }
        this.$emit("search", this.searchResult)
      }, 500);
    },

    search(propName, keyword) {
      if (!this.isRoot || this.searchValue.length <= 1) {
        return [];
      }
      const result = [];
      const recursiveExec = (data, parentPath) =>{
          const currentPath = parentPath.concat(data[propName]);
          if (data[propName].indexOf(keyword)>-1) {
            // console.log("ddd", data.title, document.getElementById(`ever_bright_tree-node-${data.title}`).getBoundingClientRect())
            result.push({...data, currentPath});
          }
          if (Reflect.has(data, 'children')) {
            const children = data.children;
            children.forEach(item=>{
              recursiveExec(item, currentPath);
            })
          }
      }
      this.nodes.forEach(item=>{
        recursiveExec(item, []);
      })
      return result;
    },



    setCursorPosition(pos) {
      if (this.isRoot) {
        this.rootCursorPosition = pos;
        return;
      }
      this.getParent().setCursorPosition(pos);
    },

    getNodes(nodeModels, parentPath = [], isVisible = true) {

      return nodeModels.map((nodeModel, ind) => {
        const nodePath = parentPath.concat(ind);
        return this.getNode(nodePath, nodeModel, nodeModels, isVisible);
      })
    },

    getNode(
      path,
      nodeModel = null,
      siblings = null,
      isVisible = null
    ) {
      const ind = path.slice(-1)[0];

      // calculate nodeModel, siblings, isVisible fields if it is not passed as arguments
      siblings = siblings || this.getNodeSiblings(this.currentValue, path);
      nodeModel = nodeModel || (siblings && siblings[ind]) || null;

      if (isVisible == null) {
        isVisible = this.isVisible(path);
      }

      if (!nodeModel) return null;

      const isExpanded = nodeModel.isExpanded == void 0 ? true : !!nodeModel.isExpanded;
      const isDraggable = nodeModel.isDraggable == void 0 ? true : !!nodeModel.isDraggable;
      const isSelectable = nodeModel.isSelectable == void 0 ? true : !!nodeModel.isSelectable;
      // if (nodeModel.title === "Item123") {
      //   path.forEach((item, index)=>{
      //     const l = index ===0 ? node.
      //   })
      //   console.log("lls", path)
      // }
      const node = {

        // define the all ISlTreeNodeModel props
        title: nodeModel.title || nodeModel.name,
        isLeaf: !!nodeModel.isLeaf,
        children: nodeModel.children ? this.getNodes(nodeModel.children, path, isExpanded) : [],
        isSelected: !!nodeModel.isSelected,
        isExpanded,
        isVisible,
        isDraggable,
        isSelectable,
        data: nodeModel.data !== void 0 ? nodeModel.data : {},

        // define the all ISlTreeNode computed props
        path: path,
        pathStr: JSON.stringify(path),
        level: path.length,
        ind,
        isFirstChild: ind == 0,
        isLastChild: ind === siblings.length - 1
      };
      return node;
    },

    isVisible(path) {
      if (path.length < 2) return true;
      let nodeModels = this.currentValue;

      for (let i = 0; i < path.length - 1; i++) {
        let ind = path[i];
        let nodeModel = nodeModels[ind];
        let isExpanded = nodeModel.isExpanded == void 0 ? true : !!nodeModel.isExpanded;
        if (!isExpanded) return false;
        nodeModels = nodeModel.children;
      }

      return true;
    },

    emitInput(newValue) {
      this.currentValue = newValue;
      this.getRoot().$emit('input', newValue);
    },

    emitSelect(selectedNodes, event) {
      selectedNodes.gap = this.gap
      this.getRoot().$emit('select', selectedNodes, event);
    },

    emitBeforeDrop(draggingNodes, position, cancel) {
      this.getRoot().$emit('beforedrop', draggingNodes, position, cancel);
    },

    emitDrop(draggingNodes, position, event) {
      this.getRoot().$emit('drop', draggingNodes, position, event);
    },

    emitToggle(toggledNode, event) {
      this.getRoot().$emit('toggle', toggledNode, event);
    },

    emitNodeClick(node, event) {
      this.getRoot().$emit('nodeclick', node, event);
    },

    emitNodeDblclick(node, event) {
      this.getRoot().$emit('nodedblclick', node, event);
    },


    select(path, event = null) {
      console.log(path, event)
      const selectedNode = this.getNode(path);
      if (!selectedNode) return null;
      const newNodes = this.copy(this.currentValue);
      let selectedNodes = [];
      this.traverse((node, nodeModel) => {
        if (node.pathStr === selectedNode.pathStr) {
          nodeModel.isSelected = !nodeModel.isSelected;
        }

        if (nodeModel.isSelected) selectedNodes = [node];

      }, newNodes);


      this.lastSelectedNode = selectedNode;
      this.emitInput(newNodes);
      this.emitSelect(selectedNodes, event);
      return selectedNode;
    },

    onMousemoveHandler(event) {
      if (!this.isRoot) {
        this.getRoot().onMousemoveHandler(event);
        return;
      }

      if (this.preventDrag) return;

      const initialDraggingState = this.isDragging;
      const isDragging =
        this.isDragging || (
        this.mouseIsDown &&
        (this.lastMousePos.x !== event.clientX || this.lastMousePos.y !== event.clientY)
      );

      const isDragStarted = initialDraggingState === false && isDragging === true;

      this.lastMousePos = {
        x: event.clientX,
        y: event.clientY
      };

      if (!isDragging) return;

      const $root = document.querySelector(".ever_bright_tree-root");
      const {top, } = $root.getBoundingClientRect();
      const $dragInfo = this.$refs.dragInfo;
      const dragInfoTop = (event.clientY - top + $root.scrollTop -20 );
      // const dragInfoLeft = (event.clientX - left);

      $dragInfo.style.top = dragInfoTop + 'px';
      // $dragInfo.style.left = dragInfoLeft + 'px';

      const cursorPosition = this.getCursorPositionFromCoords(event.clientX, event.clientY);
      const destNode = cursorPosition.node;
      const placement = cursorPosition.placement;

      if (isDragStarted && !destNode.isSelected) {
        this.select(destNode.path, event);
      }

      const draggableNodes = this.getDraggable();
      if (!draggableNodes.length) {
        this.preventDrag = true;
        return;
      }

      this.isDragging = isDragging;

      this.setCursorPosition({ node: destNode, placement });
    },

    getCursorPositionFromCoords(x, y) {
      const $target = document.elementFromPoint(x, y);
      const $nodeItem = $target.getAttribute('path') ? $target : this.getClosetElementWithPath($target);
      let destNode;
      let placement;

      if ($nodeItem) {

        if (!$nodeItem) return;

        destNode = this.getNode(JSON.parse($nodeItem.getAttribute('path')));

        const nodeHeight = $nodeItem.offsetHeight;
        const edgeSize = this.edgeSize;
        const offsetY = y - $nodeItem.getBoundingClientRect().top;


        if (destNode.isLeaf) {
          placement = offsetY >= nodeHeight / 2 ? 'after' : 'before';
        } else {
          if (offsetY <= edgeSize) {
            placement = 'before';
          } else if (offsetY >= nodeHeight - edgeSize) {
            placement = 'after';
          } else {
            placement = 'inside';
          }
        }
      } else {
        const $root = this.getRoot().$el;
        const rootRect = $root.getBoundingClientRect();
        if (y > rootRect.top + (rootRect.height / 2)) {
          placement = 'after';
          destNode = this.getLastNode();
        } else {
          placement = 'before';
          destNode = this.getFirstNode();
        }
      }

      return { node: destNode, placement };
    },

    getClosetElementWithPath($el) {
      if (!$el) return null;
      if ($el.getAttribute('path')) return $el;
      return this.getClosetElementWithPath($el.parentElement);
    },

    onMouseleaveHandler(event) {
      if (!this.isRoot || !this.isDragging) return;
      const $root = this.getRoot().$el;
      const rootRect = $root.getBoundingClientRect();
      if (event.clientY >= rootRect.bottom) {
        this.setCursorPosition({ node: this.nodes.slice(-1)[0], placement: 'after' });
      } else if (event.clientY < rootRect.top) {
        this.setCursorPosition({ node: this.getFirstNode(), placement: 'before'});
      }
    },

    getNodeEl(path) {
      this.getRoot().$el.querySelector(`[path="${JSON.stringify(path)}"]`);
    },

    getLastNode() {
      let lastNode  = null;
      this.traverse((node) => {
        lastNode = node;
      });
      return lastNode;
    },

    getFirstNode() {
      return this.getNode([0]);
    },

    onNodeMousedownHandler(event, node) {
      // handle only left mouse button
      if (event.button !== 0) return;

      if (!this.isRoot) {
        this.getRoot().onNodeMousedownHandler(event, node);
        return;
      }
      if (this.getSelected().length >0) {
        if (this.getSelected()[0].title!==node.title) {
          this.select(this.getSelected()[0].path)
        }
      }
      this.mouseIsDown = true;
    },



    onDocumentMouseupHandler(event) {
      if (this.isDragging) this.onNodeMouseupHandler(event);
    },

    onNodeMouseupHandler(event, targetNode = null) {

      // handle only left mouse button
      if (event.button !== 0) return;

      if (!this.isRoot) {
        this.getRoot().onNodeMouseupHandler(event, targetNode);
        return;
      }

      this.mouseIsDown = false;

      if (!this.isDragging && targetNode && !this.preventDrag) {
        console.log("111888")
        this.select(targetNode.path, event);
      }

      this.preventDrag = false;

      if (!this.cursorPosition) {
        this.stopDrag();
        return;
      }


      const draggingNodes = this.getDraggable();

      // check that nodes is possible to insert
      for (let draggingNode of draggingNodes) {
        if (draggingNode.pathStr == this.cursorPosition.node.pathStr) {
          this.stopDrag();
          return;
        }

        if (this.checkNodeIsParent(draggingNode, this.cursorPosition.node)) {
          this.stopDrag();
          return;
        }
      }

      const newNodes = this.copy(this.currentValue);
      const nodeModelsSubjectToInsert = [];

      // find dragging model to delete
      for (let draggingNode of draggingNodes) {
        const sourceSiblings = this.getNodeSiblings(newNodes, draggingNode.path);
        const draggingNodeModel = sourceSiblings[draggingNode.ind];
        nodeModelsSubjectToInsert.push(draggingNodeModel);
      }

      // allow the drop to be cancelled
      let cancelled = false;
      this.emitBeforeDrop(draggingNodes, this.cursorPosition, () => cancelled = true);

      if (cancelled) {
          this.stopDrag();
          return;
      }

      const nodeModelsToInsert = [];

      // mark dragging model to delete
      for (let draggingNodeModel of nodeModelsSubjectToInsert) {
          nodeModelsToInsert.push(this.copy(draggingNodeModel));
          draggingNodeModel['_markToDelete'] = true;
      }

      // insert dragging nodes to the new place
      this.insertModels(this.cursorPosition, nodeModelsToInsert, newNodes);


      // delete dragging node from the old place
      this.traverseModels((nodeModel, siblings, ind) => {
        if (!nodeModel._markToDelete) return;
        siblings.splice(ind, 1);
      }, newNodes);


      this.lastSelectedNode = null;
      this.emitInput(newNodes);
      this.emitDrop(draggingNodes, this.cursorPosition, event);
      this.stopDrag();
    },


    onToggleHandler(event, node) {
      event.stopPropagation();
      if (!this.allowToggleBranch) return;

      this.updateNode(node.path, { isExpanded: !node.isExpanded });
      this.emitToggle(node, event);
      
    },

    stopDrag() {
      this.isDragging = false;
      this.mouseIsDown = false;
      if (this.level === 0 && this.searchValue) {
        this.$emit("search", this.search("title", this.searchValue))
      }
      this.setCursorPosition(null);
    },


    getParent() {
      return this.$parent;
    },

    getRoot() {
      if (this.isRoot) return this;
      return this.getParent().getRoot();
    },

    getNodeSiblings(nodes, path) {
      if (path.length === 1) return nodes;
      return this.getNodeSiblings(nodes[path[0]].children, path.slice(1));
    },


    updateNode(path, patch) {
      if (!this.isRoot) {
        this.getParent().updateNode(path, patch);
        return;
      }

      const pathStr = JSON.stringify(path);
      const newNodes = this.copy(this.currentValue);
      this.traverse((node, nodeModel) => {
        if (node.pathStr !== pathStr) return;
        Object.assign(nodeModel, patch);
      }, newNodes);
      this.emitInput(newNodes);
    },

    getSelected() {
      const selectedNodes = [];
      this.traverse((node) => {
        if (node.isSelected) selectedNodes.push(node);
      });
      return selectedNodes;
    },

    getDraggable() {
      const selectedNodes = [];
      this.traverse((node) => {
        if (node.isSelected && node.isDraggable) selectedNodes.push(node);
      });
      return selectedNodes;
    },


    traverse(
      cb,
      nodeModels = null,
      parentPath = []
    ) {
      if (!nodeModels) nodeModels = this.currentValue;

      let shouldStop = false;

      const nodes = [];

      for (let nodeInd = 0; nodeInd < nodeModels.length; nodeInd++) {
        const nodeModel = nodeModels[nodeInd];
        const itemPath = parentPath.concat(nodeInd);
        const node = this.getNode(itemPath, nodeModel, nodeModels);
        shouldStop = cb(node, nodeModel, nodeModels) === false;
        nodes.push(node);

        if (shouldStop) break;

        if (nodeModel.children) {
          shouldStop = this.traverse(cb, nodeModel.children, itemPath) === false;
          if (shouldStop) break;
        }
      }

      return !shouldStop ? nodes : false;
    },

    traverseModels(cb, nodeModels) {
      let i = nodeModels.length;
      while (i--) {
        const nodeModel = nodeModels[i];
        if (nodeModel.children) this.traverseModels(cb, nodeModel.children);
        cb(nodeModel, nodeModels, i);
      }
      return nodeModels;
    },

    insertModels(cursorPosition, nodeModels, newNodes) {
      const destNode = cursorPosition.node;
      const destSiblings = this.getNodeSiblings(newNodes, destNode.path);
      const destNodeModel = destSiblings[destNode.ind];

      if (cursorPosition.placement === 'inside') {
        destNodeModel.children = destNodeModel.children || [];
        destNodeModel.children.unshift(...nodeModels);
      } else {
        const insertInd = cursorPosition.placement === 'before' ?
          destNode.ind :
          destNode.ind + 1;

        destSiblings.splice(insertInd, 0, ...nodeModels);
      }
    },

    insert(cursorPosition, nodeModel) {
      const nodeModels = Array.isArray(nodeModel) ? nodeModel : [nodeModel];
      const newNodes = this.copy(this.currentValue);

      this.insertModels(cursorPosition, nodeModels, newNodes);

      this.emitInput(newNodes);
    },

    checkNodeIsParent(sourceNode, destNode) {
      const destPath = destNode.path;
      return JSON.stringify(destPath.slice(0, sourceNode.path.length)) == sourceNode.pathStr;
    },

    copy(entity) {
      return JSON.parse(JSON.stringify(entity));
    }

  }
};




</script>


<style>
.ever_bright_tree {
  position: relative;
  cursor: default;
  user-select: none;
}

.ever_bright_tree-search {
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
}


.ever_bright_tree-root > .ever_bright_tree-nodes-list {
  overflow: hidden;
  position: relative;
  padding-bottom: 4px;
}

.ever_bright_tree-selected > .ever_bright_tree-node-item {
    background-color: rgb(140, 197, 255);
    color: #409eff;
}

.ever_bright_tree-node-item:hover,
.ever_bright_tree-node-item.ever_bright_tree-cursor-hover {
    color: #409eff;
}

.ever_bright_tree-node-item {
    position: relative;
    display: flex;
    flex-direction: row;

    padding-left: 10px;
    padding-right: 10px;
    line-height: 28px;
    border: 1px solid transparent;
}




.ever_bright_tree-gap {
    width: 25px;
    min-height: 1px;

}

.ever_bright_tree-toggle {
    display: inline-block;
    text-align: left;
    width: 20px;
}

.ever_bright_tree-sidebar {
    margin-left: auto;
}

.ever_bright_tree-cursor {
    position: absolute;
    /* border: 1px solid rgba(255, 0, 255, 1); */
    background: rgba(255, 0, 255, 1);
    height: 1px;
    width: 100%;
}

.ever_bright_tree-node-item.ever_bright_tree-cursor-inside {
  border: 1px solid rgba(255, 255, 0, 1);
}

.ever_bright_tree-drag-info {
  position: absolute;
  background-color: rgba(255,255,255,0.5);
  text-align: left;
  /* opacity: 0.5; */
  width: 98%;
  border: 1px solid rgba(0,0,0,0.3);
  padding-left: 20px;
  border-radius: 5px;
  box-sizing: border-box;
  /* margin-left: 20px;
  padding: 5px 10px; */

}

</style>